//==============================================================
// Name        : PerM
// Author      : Yangho Chen at University of southern California.
// Version     : 0.4.1
// Copyright   : Open source
// Description : C++, Ansi-style
//============================================================================
#include "PairedReadsMapping.h"
#include "ReadsMapping.h"
#include "Genome_Index_TableQ.h"
#include "PairedReadsSet.h"
#include "ReadInBitsSet.h"
#include "ReadInBits.h"
#include "ParameterList.h"
#include "Filename.h"
#include "chdir.h"
#include "stdafx.h"
#include <string>
#include <vector>
#include <iostream>
#if __GNUC_VERSION__ >= 40300
#include <tr1/memory>
#else
#include <memory>
#endif
using namespace std;

time_t startt, endt;

bool extractOptsFrom1stEndReadFile(ParameterList& P, vector<string>& readSetsList1)
{
    if ((int)readSetsList1.size() > 0) {
        const char* readFileName = readSetsList1.at(0).c_str();
        P.bMappedSOLiDRead = P.bMappedSOLiDRead ||\
                             is_colorspace_reads(P.readsFileFormat) ||\
                             is_colorspace_reads(readFileName);
        P.cFileFormatSymbol = getReadsFileFormatSymbol(readFileName, P.readsFileFormat);
        P.readLength = getReadLength(readFileName, P.cFileFormatSymbol);
    } else {
        LOG_INFO("Info %d: Cannot get read set from the read list.\n", ERROR_LOG);
        return(false);
    }
    return(true);
}

bool extractOptsFrom2ndEndReadFile(ParameterList& P, vector<string>& readSetsList2)
{
    if((int)readSetsList2.size() > 0) {
        const char* secondReadSetFileName = readSetsList2.at(0).c_str();
        if(P.cFileFormatSymbol == getReadsFileFormatSymbol(secondReadSetFileName, P.readsFileFormat)) {
            P.readLength2 = getReadLength(secondReadSetFileName, P.cFileFormatSymbol);
        } else {
            LOG_INFO("Info %d: The two end reads may have different formats.\n", ERROR_LOG);
            return(false);
        }
    }
    return(true);
}

bool buildIndex4SpecifiedReadLengthOnly(ParameterList& P)
{
    // This is because the second parameter can be a number that
    // specify the read length (anchor length).
    return(atoi(P.readsFile) >= (int)MIN_READ_LENGTH);
}

bool retriveReadSetsAndSettings\
(ParameterList& P, vector<string>& readSetsList1, vector<string>& readSetsList2)
{
    // (1) check reference
    if (!P.checkRefValidity()) {
        return(false);
    }
    // (2) check reads files
    if (getReadSetsFilenames(P, readSetsList1, readSetsList2)) {
        bool noError1 = extractOptsFrom1stEndReadFile(P, readSetsList1);
        bool noError2 = extractOptsFrom2ndEndReadFile(P, readSetsList2);
        if(!noError1 || !noError2) {
            return(false);
        }
    } else if (buildIndex4SpecifiedReadLengthOnly(P)) {
        P.bMakeIndex = true;
        P.bSaveIndex = true;
        P.bMappedSOLiDRead = is_colorspace_reads(P.readsFileFormat);
        P.readLength = atoi(P.readsFile); // Must call P.truncatReadLength() later.
    } else if (atoi(P.readsFile) > 0) {
        LOG_INFO("Info %d: Incorrect reads file or the read length is too short\n", ERROR_LOG);
        return(false);
    } else {
        return(false); // can not open the read file
    }
    P.truncatReadLength(); // truncated reads according to options or mapping long reads
    // (3) select seed according to the setting
    selectSeed(P);
    // (4) avoid to use the same name
    if (readSetsList1.size() > 1)
        P.outputFileN[0] = '\0';
    // (5) TODO remove the mask repeat process
    P.bMaskedMathRepeat \
        = (!P.bMatePairedReads && !P.bMappedLongRead && P.bExcludeAmbiguousReads);

    bool validSetting = true;
    validSetting &= printOptWarning4SingleEndOpts(P);
    validSetting &= printOptWarning4PairedEndOpts(P);
    return(validSetting);
}

string get_Index_Path(ParameterList P, bool is2ndTable)
{
    if (hasTheExtName(P.refFile, ".index") || P.refFormat == "index" ) {
        return(string(P.refFile));
    } else {
        int seedId = is2ndTable ? P.seedId2 : P.seedId;
        unsigned int anchorLength = is2ndTable ? P.anchorLength2 : P.anchorLength;
        string indexPath = default_index_path(getBasename\
                                              (P.refFile), P.bMappedSOLiDRead, seedId, anchorLength);
        return(indexPath);
    }
}

void setQueryIndexSubThreshold(GIndexTQ& indexTable, unsigned int subDiffThreshold)
{
    const unsigned int MAX_SUB_WHEN_QUERY_TABLE = 20;
    if (subDiffThreshold > MAX_SUB_WHEN_QUERY_TABLE) {
        indexTable.uiSubDiffThreshold = MAX_SUB_WHEN_QUERY_TABLE;
    } else {
        indexTable.uiSubDiffThreshold = subDiffThreshold;
    }
}

bool buildIndexTable(GIndexTQ& indexTable, ParameterList& P, bool is2ndTable)
{
    if (P.refFormat == "index" || hasTheExtName(P.refFile, ".index")) {
        LOG_INFO("\nInfo %d: Index file %s has incorrect format\n", ERROR_LOG, P.refFile);
    } else if (indexTable.getSeqFromFasta(P.refFile, P.refFormat)) {
        bool bMaskedMathRepeat = P.bMaskedMathRepeat && (atoi(P.readsFile) != 0);

        unsigned int anchorLength = is2ndTable ? P.anchorLength2 : P.anchorLength;
        int seedId = is2ndTable ? P.seedId2 : P.seedId;

        bool bMakeIndexSucessful = indexTable.make_index_table(anchorLength, seedId, P.bMappedSOLiDRead, bMaskedMathRepeat);
        if (bMakeIndexSucessful) {
            if (P.bSaveIndex) {
                const bool bPrintErrMsg = true;
                indexTable.save_index_table(P.indexFileN, bPrintErrMsg);
            }
            indexTable.bExcludeAmbiguous = P.bExcludeAmbiguousReads;
            setQueryIndexSubThreshold(indexTable, P.subDiffThreshold);
            return(true);
        } else {
            LOG_INFO("\nInfo %d: Faile to build index file %s.\n", ERROR_LOG, P.indexFileN);
        }
    }
    return(false);
}

/* This funciton will readin or build (1st or 2nd) index table according ParameterList
 */
bool getIndexTable(GIndexTQ& indexTable, ParameterList& P, bool get2ndTable, bool bNeed2ndTable)
{
    bool get1stTable = !get2ndTable;
    const bool bPrintWarning = false;
    string indexPath = get_Index_Path(P, get2ndTable);
    // if get the 1st table or get the 2nd table when necessary
    if(get1stTable || (get2ndTable && bNeed2ndTable)) {
        indexTable.uiSubDiffThreshold = (unsigned int) (get2ndTable ? P.subDiffThreshold2 : P.subDiffThreshold);
        if (P.bMakeIndex || indexTable.read_index_table(indexPath.c_str(), bPrintWarning) == false) {
            if (buildIndexTable(indexTable, P, get2ndTable)  == false ) {
                return(false);
            }
        }
    } //else, Do Nothing, because the 2nd indexTable is pointed to the 1st one.
    return(true);
}

bool msgOptErr(bool validSetting, ParameterList& P)
{
    if (validSetting) {
        P.printSetting();
        return(false);
    } else {
        LOG_INFO("\nInfo %d: Invalid or confusing command. Check the opt\n", CONFIG_LOG);
#ifdef WIN32
        STRIKE_KEY2CONTINUE;
#endif
        return(true);
    }
}

void mapPairedEndReads(vector<string>& readSetsList1, vector<string>& readSetsList2,
                       GIndexTQ& table1, GIndexTQ& table2, ParameterList& P)
{
    if (readSetsList1.size() > 0 && (readSetsList1.size() == readSetsList2.size())) {
        TIME_INFO(parallelMapPairedReads\
                  (readSetsList1, readSetsList2, table1, table2, P),"Mapping paired reads\n");
    } else {
        TIME_INFO(parallelMapPairedReads\
                  (readSetsList1, table1, table2, P), "Mapping paired reads");
    }
}

void mapSingleEndReads(vector<string>& readSetsList1, GIndexTQ& indexTable, ParameterList& P)
{
    if(P.bMappedLongRead) {
        TIME_INFO(parallelMappingLongReads(readSetsList1, indexTable, P),\
                  "Mapped single-ended long reads");
    } else {
        TIME_INFO(parallelMapping(readSetsList1, indexTable, P),\
                  "Mapped single-ended reads");
    }
}


/*
 * The main function construct or read in the index of a set of reference genome.
 * It maps the reads
 */
int main(int argc, const char* argv[])
{
    // testBoundedBuffer();
    // testLongBases2Colors();
    // (1) Get parameters.
    ParameterList P = getParameterList(argc, argv);
    vector<string> readSetsList1, readSetsList2;
    bool validSetting = retriveReadSetsAndSettings(P, readSetsList1, readSetsList2);
    if (msgOptErr(validSetting, P)) {
        return(-1);
    }
    // (2) Build or read index table
    bool bNeed2ndTable = P.bMatePairedReads && (P.anchorLength != P.anchorLength2);
    typedef std::tr1::shared_ptr<GIndexTQ> GIndexTQ_ptr;
    GIndexTQ_ptr indexTable1(new GIndexTQ);
    GIndexTQ_ptr indexTable2 = bNeed2ndTable ? GIndexTQ_ptr(new GIndexTQ) : indexTable1;
    bool is2ndTable;
    bool isOk = getIndexTable(*indexTable1, P, is2ndTable = false, bNeed2ndTable);
    bool isOk2 = getIndexTable(*indexTable2, P, is2ndTable = true, bNeed2ndTable);
    if(!(isOk && isOk2)) {
        return(-1);
    }
    // testGenome_Index_TableQ(&indexTable);
    // testMappingLongRead(&indexTable);
    // (3) Mapped reads.
    if (P.bMatePairedReads) {
        mapPairedEndReads(readSetsList1, readSetsList2, *indexTable1, *indexTable2, P);
    } else {
        mapSingleEndReads(readSetsList1, *indexTable1, P);
    }
    cout << endl;
    return 0;
}

